<template>
	<component :is="tagName">
		<transition
			:name="transition"
			:enter-active-class="enterActiveClass"
			:leave-active-class="leaveActiveClass"
			@after-leave="afterLeaveDestroy && doDestroy()"
		>
			<div
				ref="popper"
				:class="popperClass"
				v-show="!disabled && showPopper"
				:aria-hidden="disabled || !showPopper ? 'true' : 'false'"
				role="tooltip"
			>
				<slot>{{ content }}</slot>
			</div>
		</transition>
		<slot name="reference"></slot>
	</component>
</template>

<script>
function on(element, event, handler) {
	if (element && event && handler) {
		document.addEventListener
			? element.addEventListener(event, handler, false)
			: element.attachEvent("on" + event, handler)
	}
}

function off(element, event, handler) {
	if (element && event) {
		document.removeEventListener
			? element.removeEventListener(event, handler, false)
			: element.detachEvent("on" + event, handler)
	}
}

export default {
	props: {
		// 外层容器元素
		tagName: {
			type: String,
			default: "span",
		},
		value: Boolean,
		// 弹框位置
		placement: {
			default: "auto",
			type: String,
			validator: (value) =>
				[
					"top",
					"top-start",
					"top-end",
					"bottom",
					"bottom-start",
					"bottom-end",
					"left",
					"left-start",
					"left-end",
					"right",
					"right-start",
					"right-end",
					"auto",
					"auto-start",
					"auto-end",
				].indexOf(value) > -1,
		},
		// 定位方式
		strategy: {
			default: "",
			type: String,
			validator: (value) => ["", "absolute", "fixed"].indexOf(value) > -1,
		},
		// 触发方式
		trigger: {
			type: String,
			default: "clickToOpen",
			validator: (value) =>
				[
					"clickToOpen",
					"click", // Same as clickToToggle, provided for backwards compatibility.
					"clickToToggle",
					"hover",
					"focus",
				].indexOf(value) > -1,
		},
		// 鼠标移入延迟
		delayOnMouseOver: {
			type: Number,
			default: 10,
		},
		// 鼠标移出延迟
		delayOnMouseOut: {
			type: Number,
			default: 10,
		},
		// 禁选
		disabled: {
			type: Boolean,
			default: false,
		},
		// popper 内容主体
		content: String,
		// 过渡：移入样式
		enterActiveClass: String,
		// 过渡：移出样式
		leaveActiveClass: String,
		// 离开后销毁popper
		afterLeaveDestroy: {
			type: Boolean,
			default: false,
		},
		// 边界元素
		boundariesSelector: String,
		// reference 主体元素
		reference: String,
		// 强制显示弹框
		forceShow: {
			type: Boolean,
			default: false,
		},
		// 挂载到body下
		appendToBody: {
			type: Boolean,
			default: false,
		},
		// 启用箭头
		visibleArrow: {
			type: Boolean,
			default: true,
		},
		// 过渡动画样式
		transition: {
			type: String,
			default: "",
		},
		// 中断冒泡事件
		stopPropagation: {
			type: Boolean,
			default: false,
		},
		// 阻止默认事件
		preventDefault: {
			type: Boolean,
			default: false,
		},
		// 配置项
		options: {
			type: Object,
			default() {
				return {}
			},
		},
		// popper 根元素样式
		popperClass: {
			type: String,
			default: "",
		},
	},

	data() {
		return {
			//主体容器
			referenceElm: null,
			// popper节点
			popperJS: null,
			// 显示pooper
			showPopper: false,
			/// options 配置项
			popperOptions: {
				modifiers: [
					{
						name: "computeStyles",
						options: {
							gpuAcceleration: false, // true by default
						},
					},
				],
			},
			// 箭头
			arrowElm: "",
		}
	},

	watch: {
		value: {
			immediate: true,
			handler(val) {
				this.showPopper = val
				this.$emit("input", val)
			},
		},
		// 监听弹框开关判断是否需要启用原生方法scroll和resize
		showPopper(value) {
			if (value) {
				this.$emit("show", this)
				if (this.popperJS) {
					this.popperJS.setOptions({
						scroll: true,
						resize: true,
					})
				}
				this.updatePopper()
			} else {
				if (this.popperJS) {
					this.popperJS.setOptions({
						scroll: false,
						resize: false,
					})
				}
				this.$emit("hide", this)
			}
			this.$emit("input", value)
		},
		// 强制打开弹框
		forceShow: {
			handler(value) {
				this[value ? "doShow" : "doClose"]()
			},
			immediate: true,
		},
		// 禁用时关闭弹框
		disabled(value) {
			if (value) {
				this.showPopper = false
			}
		},
	},

	created() {
		this.setConfig()
	},

	mounted() {
		this.referenceElm = this.reference || this.$slots.reference[0].elm
		this.popper = this.$slots.default?.[0].elm || this.$refs.popper
		if (this.popper) {
			this.$utils.addClass(this.popper, "popper")
		}
		switch (this.trigger) {
			case "clickToOpen":
				on(this.referenceElm, "click", this.doShow)
				on(document, "click", this.handleDocumentClick)
				break
			case "click": // Same as clickToToggle, provided for backwards compatibility.
			case "clickToToggle":
				on(this.referenceElm, "click", this.doToggle)
				on(document, "click", this.handleDocumentClick)
				break
			case "hover":
				on(this.referenceElm, "mouseover", this.onMouseOver)
				on(this.popper, "mouseover", this.onMouseOver)
				on(this.referenceElm, "mouseout", this.onMouseOut)
				on(this.popper, "mouseout", this.onMouseOut)
				break
			case "focus":
				on(this.referenceElm, "focus", this.onMouseOver)
				on(this.popper, "focus", this.onMouseOver)
				on(this.referenceElm, "blur", this.onMouseOut)
				on(this.popper, "blur", this.onMouseOut)
				break
		}
	},

	methods: {
		setConfig() {
			// 添加箭头完成 false
			this.appendedArrow = false
			// 挂载到body下完成 false
			this.appendedToBody = false
			// 接收并合并传入的配置项
			this.popperOptions.placement = this.placement
			if (this.strategy) {
				this.popperOptions.strategy = this.strategy
			}
			this.popperOptions = Object.assign(this.popperOptions, this.options)
		},
		// 点击开关弹框
		doToggle(event) {
			if (this.stopPropagation) {
				event.stopPropagation()
			}
			if (this.preventDefault) {
				event.preventDefault()
			}
			if (!this.forceShow) {
				this.showPopper = !this.showPopper
			}
		},
		// 开启弹框
		doShow() {
			this.showPopper = true
		},
		// 关闭弹框
		doClose() {
			this.showPopper = false
		},
		// 销毁弹框
		doDestroy() {
			// 如果已打开
			if (this.showPopper) {
				return
			}
			// 如果已创建 则把节点销毁并置为null
			if (this.popperJS) {
				this.popperJS.destroy()
				this.popperJS = null
			}
			// 如果已挂载到body下则将已挂载body完成置为false并移出body下此弹框
			if (this.appendedToBody) {
				this.appendedToBody = false
				document.body.removeChild(this.popper.parentElement)
			}
		},
		// 创建弹框
		createPopper() {
			// 如果边界元素存在
			if (this.boundariesSelector) {
				// 获取边界元素
				const boundariesElement = document.querySelector(
					this.boundariesSelector
				)
				// 获取到
				if (boundariesElement) {
					if (
						!this.popperOptions.modifiers.find(
							(x) => x.name === "preventOverflow"
						)
					) {
						this.popperOptions.modifiers = [
							...this.popperOptions.modifiers,
							{
								name: "preventOverflow",
								options: {
									boundary: boundariesElement,
								},
							},
						]
					} else {
						this.popperOptions.modifiers = [
							...this.popperOptions.modifiers,
							{
								name: "preventOverflow",
								options: {
									...this.popperOptions.modifiers.find(
										(x) => x.name === "preventOverflow"
									).options,
									boundary: boundariesElement,
								},
							},
						]
					}
				}
			}
			this.$nextTick(() => {
				// 如果显示箭头则挂载箭头
				if (this.visibleArrow) {
					this.appendArrow(this.popper)
				}
				// 挂载到body下
				if (this.appendToBody && !this.appendedToBody) {
					this.appendedToBody = true
					document.body.appendChild(this.popper.parentElement)
				}
				// 销毁组件内部popperjs节点
				if (this.popperJS && this.popperJS.destroy) {
					this.popperJS.destroy()
				}

				this.popperOptions.onFirstUpdate = () => {
					this.$emit("created", this)
					this.$nextTick(this.updatePopper)
				}

				this.popperJS = this.$utils.createPopper(
					this.referenceElm,
					this.popper,
					this.popperOptions
				)
			})
		},
		// 关闭定时器
		cleanup() {
			if (this.openDelay || this.closeDelay) {
				clearTimeout(this._timer)
			}
		},
		// 销毁popper及监听
		destroyPopper() {
			off(this.referenceElm, "click", this.doToggle)
			off(this.referenceElm, "mouseup", this.doClose)
			off(this.referenceElm, "mousedown", this.doShow)
			off(this.referenceElm, "focus", this.doShow)
			off(this.referenceElm, "blur", this.doClose)
			off(this.referenceElm, "mouseout", this.onMouseOut)
			off(this.referenceElm, "mouseover", this.onMouseOver)
			off(document, "click", this.handleDocumentClick)

			this.showPopper = false
			this.doDestroy()
		},
		// 添加箭头
		appendArrow(element) {
			if (this.appendedArrow) {
				return
			}

			this.appendedArrow = true
			this.arrowElm = document.createElement("div")
			this.arrowElm.setAttribute("x-arrow", "")
			this.arrowElm.className = "popper__arrow"
			element.appendChild(this.arrowElm)
		},

		// 箭头位置判断
		setArrowPosition() {
			const referenceElmStyle = this.popperJS?.state?.rects?.reference
			const popperElmStyle = this.popperJS?.state?.rects?.popper
			const placement = this.popperJS?.state?.placement || this.placement
			if (
				!/^(top|bottom|left|right)(-start|-end)?$/g.test(placement) ||
				!this.appendedArrow
			) {
				return
			}
			if (/^(top|bottom)/g.test(placement)) {
				if (placement.indexOf("end") > -1) {
					this.arrowElm.style.left =
						referenceElmStyle.width > popperElmStyle.width
							? popperElmStyle.width / 2 - 6 + "px"
							: popperElmStyle.width -
							  referenceElmStyle.width / 2 -
							  6 +
							  "px"
				}
				if (placement.indexOf("start") > -1) {
					this.arrowElm.style.left =
						Math.min(
							referenceElmStyle.width,
							popperElmStyle.width
						) /
							2 -
						6 +
						"px"
				}
			}
			if (/^(left|right)/g.test(placement)) {
				if (placement.indexOf("end") > -1) {
					this.arrowElm.style.top =
						referenceElmStyle.height > popperElmStyle.height
							? popperElmStyle.height / 2 - 6 + "px"
							: popperElmStyle.height -
							  referenceElmStyle.height / 2 -
							  6 +
							  "px"
				}
				if (placement.indexOf("start") > -1) {
					this.arrowElm.style.top =
						Math.min(
							referenceElmStyle.height,
							popperElmStyle.height
						) /
							2 -
						6 +
						"px"
				}
			}
		},

		updatePopper() {
			const popperJS = this.popperJS
			if (popperJS) {
				popperJS.update()
				this.setArrowPosition()
			} else {
				this.createPopper()
			}
		},
		// 鼠标移入延时
		onMouseOver() {
			clearTimeout(this._timer)
			if (this.delayOnMouseOver) {
				this._timer = setTimeout(() => {
					this.showPopper = true
				}, this.delayOnMouseOver)
			} else {
				this.showPopper = true
			}
		},
		// 鼠标移出延时
		onMouseOut() {
			clearTimeout(this._timer)
			if (this.delayOnMouseOut) {
				this._timer = setTimeout(() => {
					this.showPopper = false
				}, this.delayOnMouseOut)
			} else {
				this.showPopper = false
			}
		},

		handleDocumentClick(e) {
			if (
				!this.$el ||
				!this.referenceElm ||
				this.elementContains(this.$el, e.target) ||
				this.elementContains(this.referenceElm, e.target) ||
				!this.popper ||
				this.elementContains(this.popper, e.target)
			) {
				return
			}

			this.$emit("documentClick", this)

			if (this.forceShow) {
				return
			}

			this.showPopper = false
		},

		elementContains(elm, otherElm) {
			if (typeof elm.contains === "function") {
				return elm.contains(otherElm)
			}

			return false
		},
	},
	beforeDestroy() {
		this.cleanup()
	},

	deactivated() {
		this.cleanup()
	},
	// 组件销毁时销毁弹窗
	destroyed() {
		this.destroyPopper()
	},
}
</script>
<style lang="less">
div[role="tooltip"] {
	display: inline-block;
}
.popper {
	padding: 18px 20px;
	display: inline-block;
	position: absolute;
	font-weight: normal;
	z-index: 20000;
	background: #fff;
	width: auto;
	border-radius: 4px;
	border: 1px solid #ebeef5;
	color: #606266;
	line-height: 1.4;
	text-align: justify;
	font-size: 14px;
	box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 10%);
	word-break: break-all;
	.popper__arrow {
		width: 0;
		height: 0;
		border-style: solid;
		border-width: 6px;
		border-color: transparent;
		position: absolute;
		display: block;
		margin: 0;
		filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
		&:after {
			content: "";
			border-width: 6px;
			position: absolute;
			display: block;
			width: 0;
			height: 0;
			border-color: transparent;
			border-style: solid;
		}
	}
}

.popper[data-popper-placement^="top"] {
	margin-bottom: 10px !important;
	.popper__arrow {
		border-bottom-width: 0;
		border-top-color: #ebeef5;
		bottom: -6px;
		left: calc(50% - 6px);
		margin-right: 3px;
		margin-top: 0;
		margin-bottom: 0;
		&:after {
			bottom: 1px;
			margin-left: -6px;
			border-bottom-width: 0;
			border-top-color: #fff;
		}
	}
}

.popper[data-popper-placement^="bottom"] {
	margin-top: 10px !important;
	.popper__arrow {
		border-top-width: 0;
		border-bottom-color: #ebeef5;
		top: -6px;
		left: calc(50% - 6px);
		margin-right: 3px;
		margin-top: 0;
		margin-bottom: 0;
		&:after {
			top: 1px;
			margin-left: -6px;
			border-top-width: 0;
			border-bottom-color: #fff;
		}
	}
}

.popper[data-popper-placement^="right"] {
	margin-left: 10px !important;
	.popper__arrow {
		border-left-width: 0;
		border-right-color: #ebeef5;
		left: -6px;
		top: calc(50% - 6px);
		margin-bottom: 3px;
		margin-left: 0;
		margin-right: 0;
		&:after {
			left: 1px;
			margin-top: -6px;
			border-left-width: 0;
			border-right-color: #fff;
		}
	}
}

.popper[data-popper-placement^="left"] {
	margin-right: 10px !important;
	.popper__arrow {
		border-right-width: 0;
		border-left-color: #ebeef5;
		right: -6px;
		margin-bottom: 3px;
		top: calc(50% - 6px);
		margin-left: 0;
		margin-right: 0;
		&:after {
			right: 1px;
			margin-top: -6px;
			border-right-width: 0;
			border-left-color: #fff;
		}
	}
}
</style>
